#ifndef AVALON_DATASHUTTLE_HPP
#define AVALON_DATASHUTTLE_HPP
#include <map>
#include <set>
#include <string>
#include <vector>
#include <boost/function.hpp>
#include <boost/serialization/singleton.hpp>
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
#include "Avalon/Base/Enumerator.hpp"
#include "Avalon/Base/Mask.hpp"
#include "Avalon/IO/IO.hpp"
#include "Avalon/Serialization/SerializationException.hpp"

namespace Avalon {
namespace Serialization {
namespace Detail {
  template<typename T, typename Enable = void>
  class HasShuttleMember {

    class yes { char m; };

    class no { yes m[2]; };

    struct BaseMixin {
      void Shuttle(DataShuttle*, int) {}
    };

    struct Base : public T, public BaseMixin {};

    template <typename V, V v>
    class Helper {};

    template <typename U>
    static no deduce(U*, Helper<void (BaseMixin::*)(DataShuttle*, int),
      &U::Shuttle>* = 0);

    static yes deduce(...);

  public:
    static const bool value = sizeof(yes) == sizeof(deduce((Base*)(0)));
  };

  template<typename T>
  struct HasShuttleMember<T,
      typename boost::disable_if<boost::is_class<T> >::type> {
    static const bool value = false;
  };
}

  /*! \class Version
   *  \brief Stores a type's serialization version.
   */
  template<typename T>
  struct Version {
    static const unsigned int VALUE;
  };

  template<typename T> const unsigned int Version<T>::VALUE = 0;

  /*! \class Shuttler
   *  \brief Implements shuttling for types with non-member shuttling.
   */
  template<typename T, typename Enabled>
  struct Shuttler {

    //! Shuttles a value.
    /*!
      \param shuttle The DataShuttle to use.
      \param value The value to shuttle.
      \param version The class version being serialized.
    */
    void operator()(DataShuttle* shuttle, T& value, unsigned int version)
        const {
      ::Avalon::Serialization::Shuttle(shuttle, value, version);
    }
  };

  /*! \class DataShuttle
   *  \brief Interface for moving data back and forth.
   */
  class DataShuttle {
    public:

      //! Defines a type used to construct a serialized object.
      typedef boost::function<void* ()> ConstructorType;

      //! Defines a type used to construct a shared_ptr serialized object.
      typedef boost::function<boost::shared_ptr<void> ()>
        SharedConstructorType;

      //! Constructs a DataShuttle.
      DataShuttle();

      virtual ~DataShuttle();

      //! Disables versioning, (versioning is on by default).
      void DisableVersioning();

      //! Returns <code>true</code> iff data is being received/deserialized.
      virtual bool IsReceiving() const = 0;

      //! Shuttles a bool value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, bool& value) = 0;

      //! Shuttles a bool value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const bool& value);

      //! Shuttles a char value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, char& value) = 0;

      //! Shuttles a char value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const char& value);

      //! Shuttles an unsigned char value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, unsigned char& value);

      //! Shuttles an unsigned char value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const unsigned char& value);

      //! Shuttles a short value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, short& value) = 0;

      //! Shuttles a short value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const short& value);

      //! Shuttles an unsigned short value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, unsigned short& value);

      //! Shuttles an unsigned short value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const unsigned short& value);

      //! Shuttles an int value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, int& value) = 0;

      //! Shuttles an int value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const int& value);

      //! Shuttles an unsigned int value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, unsigned int& value);

      //! Shuttles an unsigned int value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const unsigned int& value);

      //! Shuttles a long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, long& value) = 0;

      //! Shuttles a long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const long& value);

      //! Shuttles an unsigned long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, unsigned long& value);

      //! Shuttles an unsigned long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const unsigned long& value);

      //! Shuttles a long long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, long long& value) = 0;

      //! Shuttles a long long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const long long& value);

      //! Shuttles an unsigned long long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, unsigned long long& value);

      //! Shuttles an unsigned long long value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const unsigned long long& value);

      //! Shuttles a float value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, float& value) = 0;

      //! Shuttles a float value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const float& value);

      //! Shuttles a double value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, double& value) = 0;

      //! Shuttles a double value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const double& value);

      //! Shuttles a string value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      virtual void Shuttle(const char* name, std::string& value) = 0;

      //! Shuttles a string value.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      void Shuttle(const char* name, const std::string& value);

      //! Shuttles raw data.
      /*!
        \param name The name of the value.
        \param data The data to shuttle.
      */
      virtual void Shuttle(const char* name, IO::Buffer& data) = 0;

      //! Shuttles raw data.
      /*!
        \param name The name of the value.
        \param data The data to shuttle.
      */
      void Shuttle(const char* name, const IO::Buffer& data);

      //! Shuttles an enum.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      typename boost::enable_if_c<boost::is_enum<T>::value>::type
          Shuttle(const char* name, T& value) {
        Shuttle(name, reinterpret_cast<int&>(value));
      }

      //! Shuttles a constant enum.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      typename boost::enable_if_c<boost::is_enum<T>::value>::type
          Shuttle(const char* name, const T& value) {
        assert(!IsReceiving());
        Shuttle(name, const_cast<T&>(value));
      }

      //! Shuttles a generic type.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      typename boost::enable_if_c<Detail::HasShuttleMember<T>::value>::type
          Shuttle(const char* name, T& value) {
        StartStructure(name);
        unsigned int version = Version<T>::VALUE;
        if(m_versioningEnabled) {
          std::map<const std::type_info*, unsigned int>::const_iterator
            versionIterator = m_versions.find(&typeid(T));
          if(versionIterator == m_versions.end()) {
            StartStructure("__header__");
            Shuttle("version", version);
            EndStructure();
            m_versions.insert(std::make_pair(&typeid(T), version));
          }
        }
        value.Shuttle(this, version);
        EndStructure();
      }

      //! Shuttles a constant generic type.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      typename boost::enable_if_c<Detail::HasShuttleMember<T>::value>::type
          Shuttle(const char* name, const T& value) {
        assert(!IsReceiving());
        Shuttle(name, const_cast<T&>(value));
      }

      //! Shuttles a generic type.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      typename boost::enable_if_c<!boost::is_enum<T>::value &&
          !Detail::HasShuttleMember<T>::value>::type Shuttle(const char* name,
          T& value) {
        StartStructure(name);
        unsigned int version = Version<T>::VALUE;
        if(m_versioningEnabled) {
          std::map<const std::type_info*, unsigned int>::const_iterator
            versionIterator = m_versions.find(&typeid(T));
          if(versionIterator == m_versions.end()) {
            StartStructure("__header__");
            Shuttle("version", version);
            EndStructure();
            m_versions.insert(std::make_pair(&typeid(T), version));
          }
        }
        ::Avalon::Serialization::Shuttler<T>()(this, value, version);
        EndStructure();
      }

      //! Shuttles a constant generic type.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      typename boost::enable_if_c<!boost::is_enum<T>::value &&
          !Detail::HasShuttleMember<T>::value>::type Shuttle(const char* name,
          const T& value) {
        assert(!IsReceiving());
        Shuttle(name, const_cast<T&>(value));
      }

      //! Shuttles a smart pointer.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      void Shuttle(const char* name, boost::shared_ptr<T>& value) {
        StartStructure(name);
        if(IsReceiving()) {
          StartStructure("__header__");
          SharedConstructorType constructor = FindSharedConstructor();
          value = boost::static_pointer_cast<T>(constructor());
          if(value != NULL) {
            unsigned int version;
            if(m_versioningEnabled) {
              std::map<const std::type_info*, unsigned int>::const_iterator
                versionIterator = m_versions.find(&typeid(*value));
              if(versionIterator == m_versions.end()) {
                Shuttle("version", version);
                m_versions.insert(std::make_pair(&typeid(*value), version));
              } else {
                version = versionIterator->second;
              }
            } else {
              version = Version<T>::VALUE;
            }
            EndStructure();
            value->Shuttle(this, version);
          } else {
            EndStructure();
          }
        } else {
          StartStructure("__header__");
          if(value != NULL) {
            unsigned int version;
            if(m_versioningEnabled) {
              std::map<const std::type_info*, unsigned int>::const_iterator
                versionIterator = m_versions.find(&typeid(*value));
              if(versionIterator == m_versions.end()) {
                version = GetPolymorphicVersion(typeid(*value));
                Shuttle("version", version);
                m_versions.insert(std::make_pair(&typeid(*value), version));
              } else {
                version = versionIterator->second;
              }
            } else {
              version = Version<T>::VALUE;
            }
            SendSharedPointer(typeid(*value));
            EndStructure();
            value->Shuttle(this, version);
          } else {
            SendNull();
            EndStructure();
          }
        }
        EndStructure();
      }

      //! Shuttles a constant smart pointer.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      void Shuttle(const char* name, const boost::shared_ptr<T>& value) {
        assert(!IsReceiving());
        Shuttle(name, const_cast<boost::shared_ptr<T>&>(value));
      }

      //! Shuttles a pointer.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      void Shuttle(const char* name, T*& value) {
        StartStructure(name);
        if(IsReceiving()) {
          StartStructure("__header__");
          ConstructorType constructor = FindConstructor();
          value = static_cast<T*>(constructor());
          if(value != NULL) {
            unsigned int version;
            if(m_versioningEnabled) {
              std::map<const std::type_info*, unsigned int>::const_iterator
                versionIterator = m_versions.find(&typeid(*value));
              if(versionIterator == m_versions.end()) {
                Shuttle("version", version);
                m_versions.insert(std::make_pair(&typeid(*value), version));
              } else {
                version = versionIterator->second;
              }
            } else {
              version = Version<T>::VALUE;
            }
            EndStructure();
            value->Shuttle(this, version);
          } else {
            EndStructure();
          }
        } else {
          if(value != NULL) {
            StartStructure("__header__");
            SendPointer(typeid(*value));
            unsigned int version;
            if(m_versioningEnabled) {
              std::map<const std::type_info*, unsigned int>::const_iterator
                versionIterator = m_versions.find(&typeid(*value));
              if(versionIterator == m_versions.end()) {
                version = GetPolymorphicVersion(typeid(*value));
                Shuttle("version", version);
                m_versions.insert(std::make_pair(&typeid(*value), version));
              } else {
                version = versionIterator->second;
              }
            } else {
              version = Version<T>::VALUE;
            }
            EndStructure();
            value->Shuttle(this, version);
          } else {
            SendNull();
            EndStructure();
          }
        }
        EndStructure();
      }

      //! Shuttles a constant pointer.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      void Shuttle(const char* name, const T*& value) {
        assert(!IsReceiving());
        Shuttle(name, const_cast<T*&>(value));
      }

      //! Shuttles a scoped_ptr.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      void Shuttle(const char* name, boost::scoped_ptr<T>& value) {
        if(IsReceiving()) {
          T* instance;
          try {
            Shuttle(name, instance);
          } catch(std::exception&) {
            value.reset(instance);
            BOOST_RETHROW;
          }
          value.reset(instance);
        } else {
          Shuttle(name, value.get());
        }
      }

      //! Shuttles a constant scoped_ptr.
      /*!
        \param name The name of the value.
        \param value The value to shuttle.
      */
      template<typename T>
      void Shuttle(const char* name, const boost::scoped_ptr<T>& value) {
        assert(!IsReceiving());
        Shuttle(name, const_cast<boost::scoped_ptr<T>&>(value));
      }

      //! Registers a polymorphic type for serialization.
      /*!
        \param name The name of the type being registered.
        \param version The type's version.
      */
      template<typename T>
      static int RegisterType(const char* name, unsigned int version) {
        GetTypeNameToConstructor().insert(std::make_pair(name,
          &Constructor<T>));
        SetPolymorphicVersion(typeid(T), name, version);
        return 0;
      }

      //! Registers a polymorphic shared pointer type for serialization.
      /*!
        \param name The name of the type being registered.
        \param version The type's version.
      */
      template<typename T>
      static int RegisterSharedType(const char* name, unsigned int version) {
        GetTypeNameToSharedConstructor().insert(std::make_pair(name,
          &SharedConstructor<T>));
        SetPolymorphicVersion(typeid(T), name, version);
        return 0;
      }

      //! Marks the start of a structured datum.
      /*!
        \param name The name of the structured datum.
      */
      virtual void StartStructure(const char* name);

      //! Marks the end of a structured datum.
      virtual void EndStructure();

      //! Marks the start of a sequence.
      /*!
        \param name The name of the sequence.
      */
      virtual void StartSequence(const char* name);

      //! Marks the end of a sequence.
      virtual void EndSequence();

    private:
      friend class Serializer;
      friend class Deserializer;
      static std::map<std::string, ConstructorType>& GetTypeNameToConstructor();
      static std::map<std::string, SharedConstructorType>&
        GetTypeNameToSharedConstructor();
      static unsigned int GetPolymorphicVersion(const std::type_info& type);
      static const char* GetName(const std::type_info& type);
      static void SetPolymorphicVersion(const std::type_info& type,
        const char* name, unsigned int version);
      bool m_versioningEnabled;
      std::map<const std::type_info*, unsigned int> m_versions;
      virtual ConstructorType FindConstructor();
      virtual SharedConstructorType FindSharedConstructor();
      virtual void SendNull();
      virtual void SendPointer(const std::type_info& typeInfo);
      virtual void SendSharedPointer(const std::type_info& typeInfo);

      template<typename T>
      static T* Constructor() {
        return new T();
      }

      template<typename T>
      static boost::shared_ptr<T> SharedConstructor() {
        return boost::shared_ptr<T>(new T());
      }
  };

  //! Shuttles an Enumerator.
  /*!
    \param shuttle The DataShuttle to use.
    \param value The value to shuttle.
    \param version The class version being serialized.
  */
  template<typename T, typename E>
  struct Shuttler<Enumerator<T, E> > {
    void operator()(DataShuttle* shuttle, Enumerator<T, E>& value,
        unsigned int version) const {
      typename Enumerator<T, E>::Type v = value.GetValue();
      shuttle->Shuttle("value", v);
      if(shuttle->IsReceiving()) {
        value = v;
      }
    }
  };

  //! Shuttles a Mask.
  /*!
    \param shuttle The DataShuttle to use.
    \param value The value to shuttle.
    \param version The class version being serialized.
  */
  template<typename T>
  struct Shuttler<T, typename boost::enable_if<
      boost::is_base_of<BaseMask, T> >::type> {
    void operator()(DataShuttle* shuttle, T& value,
        unsigned int version) const {
      typename T::ValueType v = value.GetValue();
      shuttle->Shuttle("value", v);
      if(shuttle->IsReceiving()) {
        value = T(v);
      }
    }
  };

  //! Shuttles a vector.
  /*!
    \param shuttle The DataShuttle to use.
    \param value The value to shuttle.
    \param version The class version being serialized.
  */
  template<typename T, typename A>
  struct Shuttler<std::vector<T, A> > {
    void operator()(DataShuttle* shuttle, std::vector<T, A>& value,
        unsigned int version) const {
      if(shuttle->IsReceiving()) {
        unsigned int size;
        shuttle->Shuttle("size", size);
        value.clear();
        shuttle->StartSequence("elements");
        for(unsigned int i = 0; i < size; ++i) {
          T item;
          shuttle->Shuttle(NULL, item);
          value.push_back(item);
        }
        shuttle->EndSequence();
      } else {
        shuttle->Shuttle("size", static_cast<unsigned int>(value.size()));
        shuttle->StartSequence("elements");
        for(typename std::vector<T, A>::iterator i = value.begin();
            i != value.end(); ++i) {
          shuttle->Shuttle(NULL, *i);
        }
        shuttle->EndSequence();
      }
    }
  };

  //! Shuttles a map.
  /*!
    \param shuttle The DataShuttle to use.
    \param name The name of the value.
    \param value The value to shuttle.
    \param version The class version being serialized.
  */
  template<typename K, typename V, typename T, typename A>
  struct Shuttler<std::map<K, V, T, A> > {
    void operator()(DataShuttle* shuttle, std::map<K, V, T, A>& value,
        unsigned int version) const {
      if(shuttle->IsReceiving()) {
        unsigned int size;
        shuttle->Shuttle("size", size);
        shuttle->StartSequence("elements");
        for(unsigned int i = 0; i < size; ++i) {
          std::pair<K, V> entry;
          shuttle->Shuttle(NULL, entry);
          value.insert(entry);
        }
        shuttle->EndSequence();
      } else {
        shuttle->Shuttle("size", static_cast<unsigned int>(value.size()));
        shuttle->StartSequence("elements");
        for(typename std::map<K, V, T, A>::iterator i = value.begin();
            i != value.end(); ++i) {
          std::pair<K, V> entry = *i;
          shuttle->Shuttle(NULL, entry);
        }
        shuttle->EndSequence();
      }
    }
  };

  //! Shuttles a set.
  /*!
    \param shuttle The DataShuttle to use.
    \param name The name of the value.
    \param value The value to shuttle.
    \param version The class version being serialized.
  */
  template<typename K, typename T, typename A>
  struct Shuttler<std::set<K, T, A> > {
    void operator()(DataShuttle* shuttle, std::set<K, T, A>& value,
        unsigned int version) const {
      if(shuttle->IsReceiving()) {
        unsigned int size;
        shuttle->Shuttle("size", size);
        shuttle->StartSequence("elements");
        for(unsigned int i = 0; i < size; ++i) {
          K element;
          shuttle->Shuttle(NULL, element);
          value.insert(element);
        }
        shuttle->EndSequence();
      } else {
        shuttle->Shuttle("size", static_cast<unsigned int>(value.size()));
        shuttle->StartSequence("elements");
        for(typename std::set<K, T, A>::iterator i = value.begin();
            i != value.end(); ++i) {
          shuttle->Shuttle(NULL, *i);
        }
        shuttle->EndSequence();
      }
    }
  };

  //! Shuttles a pair.
  /*!
    \param shuttle The DataShuttle to use.
    \param name The name of the value.
    \param value The value to shuttle.
    \param version The class version being serialized.
  */
  template<typename T1, typename T2>
  struct Shuttler<std::pair<T1, T2> > {
    void operator()(DataShuttle* shuttle, std::pair<T1, T2>& value,
        unsigned int version) const {
      shuttle->Shuttle("first", value.first);
      shuttle->Shuttle("second", value.second);
    }
  };

  namespace Detail {
  namespace {
    template<typename T, unsigned int Version>
    struct SerializationRegistrar {
      const SerializationRegistrar& RegisterType(const char* name) {
        ::Avalon::Serialization::DataShuttle::RegisterType<T>(name, Version);
        return *this;
      }

      const SerializationRegistrar& RegisterSharedType(const char* name) {
        ::Avalon::Serialization::DataShuttle::RegisterSharedType<T>(name,
          Version);
        return *this;
      }
    };

    template<typename T>
    struct SerializationInitializer {};
  }
  }

  #define AVALON_SERIALIZABLE(type, name, version)                             \
  namespace Avalon {                                                           \
  namespace Serialization {                                                    \
  namespace Detail {                                                           \
  namespace {                                                                  \
    template<>                                                                 \
    struct SerializationInitializer<type> {                                    \
      static const SerializationRegistrar<type, version>& m_registrar;         \
    };                                                                         \
    const SerializationRegistrar<type, version>&                               \
      SerializationInitializer<type>::m_registrar =                            \
      ::boost::serialization::singleton<SerializationRegistrar<type,           \
      version> >::get_mutable_instance().RegisterType(name);                   \
  }                                                                            \
  }                                                                            \
  }                                                                            \
  }

  #define AVALON_SHARED_SERIALIZABLE(type, name, version)                      \
  namespace Avalon {                                                           \
  namespace Serialization {                                                    \
  namespace Detail {                                                           \
  namespace {                                                                  \
    template<>                                                                 \
    struct SerializationInitializer<type> {                                    \
      static const SerializationRegistrar<type, version>& m_registrar;         \
    };                                                                         \
    const SerializationRegistrar<type, version>&                               \
      SerializationInitializer<type>::m_registrar =                            \
      ::boost::serialization::singleton<SerializationRegistrar<type,           \
      version> >::get_mutable_instance().RegisterSharedType(name);             \
  }                                                                            \
  }                                                                            \
  }                                                                            \
  }
}
}

#endif // AVALON_DATASHUTTLE_HPP
